Глава 9. Потоки ввода-вывода

До сих пор в программных примерах мы пользовались только функциями стандартной библиотеки С. Однако в C++ имеются собственные средства, основанные на принципах классовой модели. Другими словами, в исполнительной библиотеке C++ имеется набор классов для управления вводом-выводом.

В отличие от функций буферизованного ввода-вывода С (таких, как print f и scanf, не выполняющих никаких проверок на соответствие аргументов форматной строке) классы потоков C++ безопасны в отношении типа. Ввод-вывод использует механизм перегрузки операций, гарантирующий вызов нужной функции-операции для указанного типа данных. Это главное преимущество потоков языка C++.

Классы потоков

К классам потоков относятся следующие:

Для работы с потоками вам потребуется включить в программу заголовочный файл iostream.h. Кроме того, может потребоваться подключить файлы fstream.h (файловый ввод-вывод), iomanip.h (параметризованные манипуляторы) и strstream.h (форматирование ь памяти).

Предопределенные потоки

Библиотека ввода-вывода C++ предусматривает четыре предопределенных объекта-потока, связанных со стандартными входным и выходным устройствами. Ниже дана сводка этих объектов.

Таблица 9.1. Предопределенные объекты-потоки C++

Имя

Класс

Описание

cin

istream

Ассоциируется со стандартным вводом (клавиатурой).

cout

ostream

Ассоциируется со стандартным выводом (экраном).

cerr

ostream

Ассоциируется со стандартным устройством ошибок (экраном) без буферизации.

clog

ostream

Ассоциируется со стандартным устройством ошибок (экраном)с буферизацией.

 

Операции извлечения и передачи в поток

Основными классами ввода-вывода C++ являются istream и ostream. Первый из них перегружает операцию правого сдвига (>>), которая служит в нем для ввода данных и называется операцией извлечения из потока. Класс ostream перегружает соответственно операцию левого сдвига (<<); она применяется для вывода и называется операцией передачи в поток.

Нужно сказать, что стандартной русской терминологии как таковой в C++ не существует. Каждый изобретает свою собственную; иногда удачно, иногда — нет.

Вот простейшие операторы ввода и. вывода на стандартных потоках:

#include <iostream.h>

int main()

{

char name [ 8.0] ;

cout<< "Enter your name: ";

cin>> name;

cout <<"Hello " << name << "!";

return 0;

}

Как видите, при действиях над потоками возможно последовательное сцепление операций, подобно последовательному присваиванию. Как вы уже знаете, такая форма записи обеспечивается благодаря тому, что функции-операции извлечения и передачи возвращают ссылку на свой объект.

Перегруженные операции для встроенных типов

Классы istream и ostream перегружают операции соответственно извлечения и передачи в поток для всех встроенных типов. Это позволяет единообразно применять эти операции для чтения и вывода символов, целых, вещественных чисел (т. е. с плавающей точкой) и строк. Вот небольшая иллюстрация, где попутно показан еще простейший прием проверки на ошибку при вводе:

#include <iostream.h>

void check(void) {

if (!cin.good())

{

// Либо просто if (!cin) {

cout << "Error detected!";

exit (1);

}

int main(void)

{

double d;

long 1;

cout << "Enter a floating point value: ";

cin >> d;

check () ;

cout << "You entered: " << d << '\n';

cout << "Enter an integer value: ";

cin >> 1;

check () ;

cout << "You entered: " << 1 << '\n';

return 0;

}

Операции извлечения и передачи в поток (соответственно для классов istream и ostream) можно перегрузить таким образом, чтобы можно было применять их для ввода или вывода объектов класса, определенного пользователем. Приведенный ниже пример демонстрирует эту методику. Вообще-то в подобных случаях совершенно необходимо предусмотреть детектирование и обработку ошибок ввода, но здесь мы этого не сделали.

#include <iostream.h>

class Point { int x, у;

public:

Point(int xx = 0, int yy = 0) {

x = xx; у = yy;

}

friend istream &operator>>(istream&, Points);

friend ostream &operator“(ostream&, Points);

};

istream &operator”(istream &is, Point &p)

//

// При вводе точка представляется просто парой чисел,

// разделенных пробелом.

// is >> р.х > р.у;

return is;

}

ostream &operator<<(ostream &os.Point &p) {

//

// Вывод в виде (х, у).

//

os<< ' ( '<< р. х<< ", "<< р. у<<') ' ;

return os;

}

int main() {

Point р;

cout<< "Enter point coordinates: ";

cin>> р;

cout<< "The point values are " << р;

return 0;

}

Форматирование

Библиотека ввода-вывода предусматривает три способа форматирования: посредством вызова форматирующих функций-элементов, с помощью манипуляторов или путем установки или сброса флагов потока.

Форматирующие функции-элементы

Эти функции являются элементами класса ios и перегружены таким образом, чтобы можно было либо читать, либо устанавливать значение соответствующего атрибута потока. Если аргумент в вызове отсутствует, функция возвращает текущее значение атрибута. Если аргумент указан, функция устанавливает новое и возвращает предыдущее значение атрибута.

long width(long)

Эта функция предназначена для чтения или установки атрибута ширины поля.

char fill(char)

Функция позволяет прочитать или установить текущий символ заполнения.

По умолчанию символ заполнения — пробел.

long precision(long)

Эта функция позволяет прочитать или установить значение атрибута точности, определяющего либо общее число выводимых цифр, либо число цифр дробной части.

Пример

Ниже приводится программа, демонстрирующая форматирование потока с помощью функций-элементов класса ios.

Листинг 9.1. Демонстрация форматирующих функций потока

///////////////////////////////////////////////

// Format.срр: Форматирующие функции-элементы ios.

//

#include <iostream.h>

#pragma hdrstop

#include <condefs.h>

#pragma argsused

int main(int argc, char* argv[])

{

//

// Ширина поля при вводе и выводе.

//

cnar sir [16];

cout<< "Enter something: ";

cin.width(16); // Ввод не более 15 символов. cin>> str;

cout.width(32); // Вывести в поле шириной 32. cout<< str<< "\n\n";

//

// Заполняющий символ и ширина поля. Ширина сбрасывается

// после каждой операции, поэтому она устанавливается

// для каждого числа.

//

int h = 7, m = 9, s = 0; // Выводятся в виде hh:mm:ss.

cout.fill('0'); cout << "Time is ";

cout.width (2); cout << h << ' : ' ; cout.width (2) ;

cout<< m<< ' : ' ;

cout.width (2) ;

cout<< s<< ".\n\n";

cout.fill (' '); // Восстановить пробел.

//

// Точность.

//

double d = 3.14159265358979;

float f = 27182.81828;

cout.precision (5);

cout << f << '\n'; . // Выводит "27183" .

cout << d << '\n'; ' // Выводит "3.1416".

cout .precision (4) ;

cout << f << '\n'; // Выводит "2.718е+04".

cout.setf(ios::fixed); // Установить флаг fixed.

cout<< f<<'\n'; // Выводит "27182.8184".

return 0;

}

Манипуляторы

Манипуляторы потоков являются по существу функциями, которые можно вызывать непосредственно в цепочке операций извлечения или передачи в поток. Различают простые и параметризованные манипуляторы. У простых манипуляторов аргументы отсутствуют. Параметризованные манипуляторы имеют аргумент.

Ниже приводится сводка имеющихся манипуляторов, как простых, так и параметризованных. Они Перечислены в алфавитном порядке.

Таблица 9.2. Простые и параметризованные манипуляторы

Манипулятор

Описание

dec

Задает десятичную базу преобразования.

end1

Передает в поток символ новой строки и сбрасывает поток.

ends

Передает в поток символ завершающего строку нуля.

flush

Сбрасывает выходной поток.

hex

Задает шестнадцатеричную базу преобразования.

lock(ios Sir)

Блокирует дескриптор файла потока ir.

oct

Задает восьмеричную базу преобразования.

resetiosflags(int f)

Сбрасывает флаги, биты которых установлены в f.

setbase(int b)

Устанавливает базу преобразования (0, 8, 10 или 16).

setiosflags(int f)

Устанавливает флаги, биты которых установлены в f.

setfill(int c)

Задает символ заполнения (аналогичен функции

fiilO).

setprecision(long p)

Задает точность (аналогичен функции precision ()).

setw(iong w)

Задает ширину поля (аналогичен функции width ()).

lunlock(ios &ir)

Разблокирует дескриптор файла для потока ir.

ws

Исключает начальные пробельные символы.

 

Вот пример использования некоторых манипуляторов (мы создали один свой собственный):

Листинг 9.2. Форматирование с помощью манипуляторов

/////////////////////////////////////////////////

// Manip.cpp: Демонстрация некоторых манипуляторов.

//

#include <iomanip.h>

#pragma hdrstop

#include <condefs.h>

//////////////////////////////////////////////////

// Манипулятор, определенный пользователем - звонок.

//

ostream shell(ostream &os)

{

return os<< '\a';

#pragma argsused

int main(int argc, char* argv[])

{

cout “ bell; // Тестирование манипулятора bell.

//

// Манипуляторы базы преобразования.

//

long 1 = 123456;

cout<< "Hex: "<< hex<< 1<< end1

<<"Oct: "<< oct<< 1<< end1

<< "Dec: " << dec << 1 << end1;

//

// Параметризованные манипуляторы.

//

int h=12, m=5, s=0; // To же, что в примере

// Format.cpp. cout << "The time is " << setfill('0')

<< setw(2) << h << ':'

<< setw(2) << m << ':'

<< setw(2) << s << setfillC ') << end1;

return 0;

}

Как видите, очень несложно определить свой собственный простой манипулятор. Это всего лишь функция, возвращающая ссылку на переданный ей в параметре поток.

Создать параметризованный манипулятор не так просто. Существуют различные способы сделать это, но наиболее очевидный из них — реализация манипулятора через класс эффектора. Идея состоит вот в чем. Нужно определить для манипулятора собственный класс с конструктором, принимающим нужные параметры, •и перегрузить для этого класса операцию передачи (извлечения) соответствующего потока. После этого конструктор можно вызывать в качестве параметризованного манипулятора. Создается временный объект, который выводится в поток перегруженной операцией и удаляется. Ниже показан манипулятор, который выводит в поток свой аргумент типа unsigned в двоичной форме.

#include <iostream.h>

// Класс эффектора.

class Bin {

int val;

public:

Bin(unsigned arg) { val = arg; }

friend ostream &operator“(ostreams. Bin);

};

// Вывод числа в двоичной форме.

ostream &ooerator<<(ostream &os. Bin b) {

int cb = 1; // Контрольный бит для отсчета циклов.

do {

if (b.val <0) // Если val < 0, то старший бит = 1. os << 1;

else

os<< 0;

} while (b.vai<<= 1, cb<<= 1) ;

return os;

}

int main ()

(

unsigned n = Ox00ff0f34;

cout<< "Some binary: "<< Bin(n)<< end1;

return 0;

}

Рис. 9.1 Манипулятор, выводящий свой аргумент в двоичной форме

Форматирующие флаги

Флаги управления форматированием являются битовыми полями, хранящимися в переменной типа fmtflags (псевдоним int). Для их чтения и/или модификации могут применяться следующие функции-элементы класса ics:

Помимо функций, для управления флагами можно пользоваться манипуляторами setiosflags (аналог setf() с одним параметром) и reset-iosflags (аналог unsetf ()).

В таблице 9.3 описаны форматирующие флаги потоков.

Таблица 9.3. Форматирующие флаги класса ios

Флаг

Описание

internal

Если установлен, при выводе чисел знак выводится на левом краю поля вывода, а само число выравнивается по правому краю поля. Промежуток заполняется текущим символом заполнения.

dec

Устанавливает десятичное представление чисел. Принимается по умолчанию.

oct

Устанавливает восьмеричное представление чисел.

hex

Устанавливает шестнадцатеричное представление чисел.

showbase

Если установлен, то при восьмеричном и шестнадцатеричном представлении чисел выводит индикатор основания (0 для восьмеричных и Ох для шестнадцатеричных чисел).

showpoint

Если установлен, для вещественных чисел всегда выводится десятичная точка.

uppercase

Если установлен, шестнадцатеричные цифры от А до F, а также символ экспоненты Е выводятся в верхнем регистре.

boolalpfa

Если установлен, булевы значения выводятся как слова “true/false”. В противном случае они представляются соответственно единицей и нулем.

showpos

Выводит + для положительных чисел.

scientific

Если установлен, вещественные числа выводятся в научной (экспоненциальной) нотации.

fixed

Если установлен, вещественные числа выводятся в десятичном формате (с фиксированной точкой).

unitbuf

Если установлен, поток сбрасывается после каждой операции передачи.

 

Несколько замечаний относительно перечисленных в таблице флагов.

Имена перечисленных выше флагов и других констант принадлежат к области действия класса ios. Вне этого класса нужно либо воспользоваться разрешением области действия (ios : : scientific), либо обращаться к ним, как к элементам существующего объекта (cout. scientific). Мы поедпочитаем первый способ.

Листинг 9.3. форматирующие флаги потоков

////////////////////////////////////////////////////

// Flags.срр: Форматирующие флаги потоков.

//

#include <iostream.h>

#include <iomanip.h> #pragma hdrstop

#include <condefs.h>

#pragma argsused

int main(int argc, char* argv[])

{

//

// Демонстрация флага skipws. Если его сбросить, то при

// наличии начальных пробелов при вводе возникает ошибка.

//

long 1;

cout<< "Enter an integer: ";

cin.unsetf(ios::skipws);

cin >> 1;

if (cin) // При ошибке потока

cin == NULL. cout<< "You entered "<< 1<< endl;

else {

cout << "Incorrect input."<< endl;

cin.clear (); // Обнуление битов ошибки.

} cout<<endl;

//

// Демонстрация флагов основания и знака.

// Задается основание 16, вывод индикатора и знака +.

//

1 = 8191;

cout.setf(ios::hex, ios::basefield);

cout.setf(ios::showbase | ios::showpos);

cout << "hex: " <<1 << oct // Изменим основание

<< " oct: "<< 1 << dec // манипулятором.

<< " dec: " << 1 << endl;

cout << endl;

//

// Демонстрация флагов формата вещественных чисел.

//

double dl = 1.0е9, d2 = 34567.0;

cout <<"Default: " << dl << " "<<d2 << end1;

// Вывод десятичной точки. cout.setf(ios::showpoint);

cout << "Decimal: " << dl<< " " << d2 << endl;

// Нотация с фиксированной точкой.

// Заодно сбросим вывод знака +.

cout.setf(ios::fixed, ios::floatfield | ios::showpos);

cout << "Fixed: " << dl << " " << d2 << endl;

cout<< endl;

//

// Вывод булевых значений как "true/false".

//

bool b = true;

cout.setf(ios::boolalpha) ;

cout << "Boolean values: " << b << '' << !b endl;

return 0;

}

Рис. 9.2 Демонстрация флагов форматиоования потока

Состояние потока

Состояние объекта класса ios (и производных от него) содержится в его закрытом элементе _state в виде набора битов. Следующая таблица перечисляет имеющиеся биты состояния потока.

Таблица 9.4. Биты состояния потока

Бит

Описание

goodbit

С потоком все в порядке (на самом деле это не какой-то бит, а 0 — отсутствие битов ошибки).

eofbit

Показывает, что достигнут конец файла.

failbit

Индицирует ошибку формата или преобразования. После очистки данного бита работа с потоком может быть продолжена.

badbit

Индицирует серьезную ошибку потока, связанную обычно с буферными операциями или аппаратурой. Скорее всего, поток далее использовать невозможно.

 

Для опроса или изменения состояния потока в классе ios имеется ряд функций и операций.

Функция operator void*() неявно вызывается, если поток сравнивается с нулем (как cin в примере из листинга),

Файловые потоки

Файловые потоки библиотеки ввода-вывода реализуют объектно-ориентированную методику работы с дисковыми файлами. Имеется три класса таких потоков:

Эти классы выводятся соответственно из istream, ostream и iostream. Таким образом, они наследуют все их функциональные возможности (перегруженные операции << и>>” для встроенных типов, флаги форматирования и состояния, манипуляторы и т. д.).

Чтобы работать с файловым потоком, нужен, во-первых, объект потока, а во-вторых, открытый файл, связанный с этим объектом.

Конструирование объекта потока

Каждый из трех классов файловых потоков имеет четыре конструктора.

ifstream () ;

of stream();

fstream () ;

if stream(const char *name,

int mode = ios::in, long prot = 0666);

ofstream(const char *name,

int mode = ios::out, long prot = 0666);

fstream (const char *name, int mode, long prot = 0666);

ifstreamfint file);

ofstream(int file);

fstream (int file) ;

ifstream(int file, char *buf, int len)

of stream(int file, char *buf, int len)

fstream (int file, char *buf, int len)

Режимы открытия файла

Параметр mode, который имеет вторая форма конструктора, задает режим открытия файла. Для значений параметра класс ios определяет символические константы, перечисленные в таблице 9.5.

Таблица 9.5. Константы класса ios для режимов открытия файла

Константа

Описание

арр

Открытие для записи в конец файла.

ate

При открытии позиционирует указатель на конец файла.

binary

Файл открывается в двоичном (не текстовом) режиме.

in

Файл открывается для ввода.

out

Файл открывается для вывода.

trunc

Если файл существует, его содержимое теряется.

 

Константы можно комбинировать с помощью поразрядного OR. Для конструкторов классов if stream и ofstream параметр mode имеет значения по умолчанию — соответственно ios : : in и ios : : out.

Закрытие файла

В классах файловых потоков имеется функция close (), которая сбрасывает содержимое потока и закрывает ассоциированный с ним файл.

Кроме того, деструктор потока автоматически закрывает файл при уничтожении объекта потока.

При ошибке закрытия файла устанавливается флаг failbit.

Примеры файловых потоков

Следующий пример (листинг 9.4) демонстрирует различные режимы и способы открытия потока.

Листинг 9.4. Примеры открытия файловых потоков

/////////////////////////////////////////////////////////

// Filemode.срр: Режимы открытия файлов.

//

#include <f stream .,h>

#include <string.h>

#pragma hdrstop

#include <condefs.h>

char *data[] = {"It's the first line of test data.",

"Second ,line.",

"Third line.",

"That's enough!"};

//

// Функция для распечатки содержимого файла. //

int Print(char *fn) {

char buf[80] ;

ifstream ifs(fn) ;

if (!ifs) {

cout <<fn<< " - Error reading file." << endl;

return -1;

} while (ifs) {

ifs.getline(buf, sizeof(buf)) ;

if (ifs)

cout << buf<< end1;

} return 0;

}

#pragma argsused

int main(int argc, char* argv[])

{

char name[]= "Newfile.txt";

fstream fs(name, ios::in);

if (fs) { // Файл уже существует. cout “ name “ " - File already exists." << endl;

} else { // Создать новый файл.

cout<< name<< " - Creating new file."<< endl;

fs.open(name, ios::out);

for (int i=0; i<3; i++) fs << data[i] << endl;

}

fs.close () ;

cout << end1;

//

// Файл либо уже существовал, либо мы его только что

// создали. Распечатаем его.

// Print(name);

cout << endl;

//

// Допишем строку в конец файла.

// fs.open(name, ios::app);

if (rs) {

fs M<< data[3]<< endl;

fs.close ();

} Print(name);

return 0;

}

Рис. 9.3 Результат работы программы Filemode

Для чтения строки из файла мы применили в программе функцию getline () , которая будет подробно описана чуть позже.

Бесформатный ввод-вывод

До сих пор речь у нас шла почти исключительно о вводе-выводе с использованием операций извлечения/передачи данных. Эти операции перегружены для всех встроенных типов и выполняют соответствующие преобразования из внутреннего представления данных в текстовое и из текстового во внутреннее (машинное).

Однако в библиотеке C++ имеется немало функций бесформатного ввода-вывода, которые часто применяют для чтения и записи двоичных (не-текстовых) файлов.

Двоичный режим ввода-вывода

Двоичный режим открытия файла (с установленным битом binary) означает, что никакой трансляции данных при передаче из файла в поток и обратно производиться не будет. Речь здесь идет не о форматных преобразованиях представления данных. При текстовом режиме (он принимается по умолчанию) при передаче данных между файлом и потоком производится замена пар символов CR/LF на единственный символ LF (' \n ') и наоборот. Это происходит до преобразований представления, которые выполняются операциями извлечения/передачи. Двоичный ввод-вывод означает всего-навсего, что такой замены происходить не будет; тем не менее двоичный режим необходим при работе с сырыми данными, т. е. данными в машинной форме без преобразования их в текстовый формат.

Чтобы открыть файл в двоичном режиме, нужно, как уже упоминалось, установить в параметре mode конструктора потока или функции open() бит ios::binary.

Чтение и запись сырых данных

Чтение сырых данных производится функцией read () класса istream:

istream &read(char *buf, long len);

Здесь buf — адрес буфера, в который будут читаться данные, а len — число символов, которые нужно прочитать.

Запись сырых данных производится функцией write () класса ostream. Она выглядит точно так же, как функция read () :

ostream &write(char *buf, long len);

Здесь buf — адрес буфера, в котором содержатся данные, а len — число символов, которые должны быть записаны в поток.

Обе функции возвращают ссылку на свой объект-поток. Это означает, что возможны их цепные вызовы, т. е. выражения вроде

ostream os (...);

os.write(...).write (...).write(...) ;

Вот небольшой пример записи и чтения сырых данных:

#include <iostream.h>

#include <fstream.h>

int main(void) {

char name[] = "testfile.dat";

int i = 1234567;

double d = 2.718281828;

//

// Открытие выходного потока в двоичном режиме

//и запись тестовых данных.

//

ofstream ofs(name, ios::out | ios::binary);

if (ofs) {

ofs.write((char*)&i, sizeof(i)); // Целое.

ofs.write((char*)&d, sizeof(d)); // Вещественное.

ofs.write(name, sizeof(name)); // Строка. ofs.close ();

}

//

// Открытие входного потока в двоичном режиме.

// if stream ifs(name, ios::in | ios::binary) ;

i = 0; //

d = 0; // Уничтожить данные.

name[0] = '\0'; //

//

// Прочитать данные.

//

if (ifs) {

ifs.read((char*)&i, sizeof (i));

ifs.read((char*)&d, sizeof(d));

ifs.read(name, sizeof(name));

ofs.close () ;

} //

// Проверка - напечатать прочитанные данные. //

cout “ "Data read from file: i = " << i<< ", d = " << d

<< ", name = " << name << endl;

return 0;

}

Некоторые функции потоков

В классах istream и ostream есть ряд функций, которые позволяют выполнять над потоками разные полезные операции (в основном при бесформатном вводе-выводе). Здесь мы опишем наиболее часто употребляемые из них.

Класс istream

Следующие функции являются элементами класса istream:

Устанавливает положение указателя потока. Для первой формы указывается абсолютная, для второй — относительная позиция указателя. Параметр dir может принимать следующие значения:

Класс ostream

Последним двум функциям из istream соответствуют аналогичные функции класса ostream:

Чтение символов и строк

Для чтения одиночных символов, а также строк применяется функция get класса istream. Эта функция перегружена следующим образом:

int get () ;

istream &get(char &c) ;

istream &get(char *buf, long len, char t = '\n');

Две первые формы функции предназначены для извлечения из потока одиночного символа. Функция int get() возвращает символ в качестве своего значения. Функция get (char &c) передает символ в параметре и возвращает ссылку на свой поток.

Вот, например, как можно было бы выполнить посимвольное копирование файлов:

ifstream ifs("infile.dat");

ofstream ofs("outfile.dat");

while (ifs & ofs)

ofs.put(ifs.get());

// put (char) передает в поток

// одиночный символ.

Последняя форма функции get () извлекает из потока последовательность символов. Символы читаются в буфер buf, пока не произойдет одно из следующих событий:

get ():

istream Sgetline(char *buf, long len, char t = '\n');

Разница между этими двумя функциями состоит в том, что get line () извлекает из потока ограничивающий символ, в то время как get () этого не делает. Ни та, ни другая функция не записывает ограничивающий символ в буфер.

Пример использования getline () вы уже видели в листинге 9.4. Вот небольшой пример чтения строк с помощью get ():

#inciude <iostream.h>

int main(void) {

char name[9], ext[4];

cout << "Enter a filename with extension: ";

cin.get(name, 9, '.');

cin.ignore (80, '.'); // Удалить все оставшиеся

// до точки символы. cin.get(ext, 4) ;

cin.ignore(80, '\n'); // Удалить все, что осталось

// в строке.

cout<< "Name: "<< name << "; extension: " << ext << endl;

return 0;

}

Эта программа, как нетрудно догадаться, усекает произвольное имя файла до формата 8.3.

Ввод-вывод с произвольным доступом

Понятие произвольного доступа к файлу подразумевает два, или даже три, различных момента. Во-первых, оно означает, что можно произвольно обращаться к любой записи или любому байту в файле, в противоположность последовательному доступу, когда данные извлекаются или передаются в поток строго по очереди. Во-вторых, предполагается, что на открытом файле можно произвольно чередовать операции чтения и записи. И, наконец, из сказанного вытекает, что ввод-вывод с произвольным доступом является по преимуществу бесформатным.

Приведенная ниже программа открывает (создает новый или переписывает старый) свой файл как двоичный, и, кроме того, сразу для ввода и вывода. Она применяет функции позиционирования потока и функции бесформатного чтения-записи.

Листинг 9.5. Произвольный доступ к файлу

//////////////////////////////////////////////////

// Random.cpp: Демонстрация файла с произвольным доступом.

//

#include <fstream.h>

#include <iomanip.h>

#pragma hdrstop

#include <condefs.h>

const int NP = 10;

const int IS = sizeof(int);

#pragma argsused

int main(int argc, char* argv[])

{

int pt, i;

//

// Открытие файла для чтения/записи.

//

fstream fs("random.pts",

ios::binary | ios::in | ios::out | ios::trunc);

if (ifs) {

cerr << "Failed to open file." << endl;

return (1);

}

//

// Первоначальная запись файла.

//

cout << "Initial data:" << endl;

for (i=0; i<NP; i++){

pt = i;

fs.write((char*)&pt, IS);

cout << setw(4) << pt;

}

cout << endl << endl;

//

// Чтение файла от конца к началу.

//

cout << "Read from the file in reverse order:"<< endl;

for (i=0; i<NP; i++) {

fs.seekg(-(i + 1) * IS, ios::end);

fs.read((char*)&pt, IS);

cout “ setw(4)<< pt; . }

cout<< end1 << end1;

//

// Переписать четные индексы.

//

for (i=l; i<NP/2; i++) {

fs.seekg(2 * i * IS) ;

fs.read((char*)&pt, IS);

pt = -pt;

fs.seekg(fs.tellg () - IS); // Возврат на шаг.

fs.write((char*)&pt, IS);

}

//

// Распечатать файл.

//

cout << "After rewriting the even records:"<<endl;

fs.seekg(0) ;

for (i=0; i<NP; i++) {

fs.read((char*)&pt, IS);

cout << setw(4) << pt;

}

cout << endl;

fs.close ();

return 0;

}

Когда эта программа открывает уже существующий файл, он усекается до нулевой длины (т. е. все его данные теряются). Если вы хотите работать с имеющимися в файле данными, нужно убрать бит ios: :trunc из режима открытия потока. Кстати, в примере это можно сделать безболезненно — данные файла все равно сразу переписываются заново.

В этом примере мы пользовались для позиционирования потока функцией seekg () . Но поскольку поток у нас типа f stream, и открыт он в режиме чтения-записи, то все равно, какую функцию применять для позиционирования — seekg () или seekp () .

He следует упускать из виду, что при выполнении операций бесформатного чтения или записи (read/write) указатель потока сдвигается вперед на число прочитанных (записанных) байтов.

Вывод программы показан на рис. 9.4.

Рис. 9.4 Программа Random

Заключение

Аппарат потоковых классов библиотеки C++ довольно громоздок, если сравнивать его, например, с функциями языка С вроде printf (). Однако средства потоков C++ единообразны, надежны и расширяемы. Как вы узнали из этой главы, можно достаточно просто перегрузить операции извлечения и передачи, чтобы с точки зрения потоков ввода-вывода определенный вами тип выглядел бы подобно встроенным типам данных.

В следующей главе мы займемся шаблонами — средством C++, которое позволяет создавать “обобщенные” классы, служащие моделью некоторого множества конкретных классов.